import {Post} from "knex/types/tables"
import {AggregateSteps, SchemaFieldTypes} from "redis"
import {redisSQLDatabase} from "TYPE"
import {cache, createIndex} from "../cache"
import knex from '../sql'
import {subareaDB} from "./subarea.db"
import {userDB} from "./user.db"
import {RedisSearchLanguages} from "@redis/search/dist/commands"
import {tokenizer} from "../../chinese_analyzer"
import {topicDB} from "./topic.db"

declare module 'knex/types/tables' {
    interface Post {
        pid: number
        ppid: number
        type: number
        topic: number
        title: string
        content: string
        views: number
        subpost_num: number
        author: number
        subarea: number
        status: number
        deleted: boolean
        create_time: Date
        modify_time: Date
    }
    interface Tables {
        posts: Post
    }
}
export interface QueryPostOption {
    paged?: number,
    posts_per_page?: number,
    topic?: number,
    subarea?: number,
    author?: number,
    ppid?: number,
    searchKey?: string,
    onlyTopic?: boolean,
    sortby?: ("active_time" | "update_time" | "create_time"),
    direction?: ("DESC" | "ASC")
}
class PostDB implements redisSQLDatabase {

    indexName = 'idx:posts' as const
    // get post by id from cache 
    async getPostById(pid: number) {
        let post: Post = await cache.json.get(`post:` + pid) as any
        return post
    }
    async initIndex(del = false) {
        if (del) {
            await cache.ft.dropIndex(this.indexName)
        }
        await createIndex(this.indexName, {
            '$.author': {
                type: SchemaFieldTypes.TAG as any,
                AS: 'author'
            },
            '$.title': {
                type: SchemaFieldTypes.TEXT,
                AS: 'title'
            },
            '$.content': {
                type: SchemaFieldTypes.TEXT,
                AS: 'content'
            },
            '$.ppid': {
                type: SchemaFieldTypes.TAG,
                AS: 'ppid'
            },
            '$.subarea': {
                type: SchemaFieldTypes.TAG as any,
                AS: 'subarea'
            },
            '$.topic': {
                type: SchemaFieldTypes.TAG as any,
                AS: 'topic'
            },
            '$.topic_type': {
                type: SchemaFieldTypes.TAG as any,
                AS: 'topic_type'
            },
            '$.type': {
                type: SchemaFieldTypes.TAG as any,
                AS: 'type'
            },
            '$.deleted': {
                type: SchemaFieldTypes.TAG as any,
                AS: 'deleted'
            },
            '$.views': {
                type: SchemaFieldTypes.NUMERIC as any,
                SORTABLE: true,
                AS: 'views'
            },
            '$.active_time': {
                type: SchemaFieldTypes.NUMERIC as any,
                SORTABLE: true,
                AS: 'active_time'
            },
            '$.create_time': {
                type: SchemaFieldTypes.NUMERIC as any,
                SORTABLE: true,
                AS: 'create_time'
            },
            '$.modify_time': {
                type: SchemaFieldTypes.NUMERIC as any,
                SORTABLE: true,
                AS: 'modify_time'
            },
        }, {
            ON: 'JSON', PREFIX: 'post:', LANGUAGE: RedisSearchLanguages.CHINESE
        })
    }
    convertToRedisJSON(post: Post) {
        post.create_time = post.create_time.valueOf() as any
        post.modify_time = post.modify_time.valueOf() as any
        (post as any).lastCountViewsTime = Date.now();
        (post as any).active_time = post.modify_time;
        (post as any).status += '';
        (post as any).deleted += '';
        (post as any).subarea += '';
        (post as any).topic += '';
        (post as any).ppid += '';
        (post as any).author += '';
        (post as any).type += '';
        (post as any).subpost_num = 0;
    }
    async updateActiveTime(pid: number) {
        return cache.json.set(`post:` + pid, '$.active_time', Date.now())
    }
    async processPostWhenLoading(post: any) {
        this.convertToRedisJSON(post)
        if (post.type == 1) {
            if (post.topic && post.topic != 'null') {
                cache.json.set(`topic:` + post.topic, '$.pid', post.pid)
            }
            let subarea = subareaDB.allSubareas[post.subarea] || await subareaDB.loadSubareaById(post.subarea)
            post.topic_type = "" + subarea.type
            await cache.json.set(`post:` + post.pid, '$', post as any);
        } else {
            if (post.ppid)
                cache.json.numIncrBy('post:' + post.ppid, '$.subpost_num', 1)
            cache.json.set(`post:` + post.pid, '$', post as any)
        }
    }
    async loadAll() {
        await cache.keys('post:*').then(async (keys) => {
            for (const key of keys) {
                await cache.del(key)
            }
        })
        await knex('posts').select().then(async ps => {
            for (let post of ps) {
                await postDB.processPostWhenLoading(post)
            }
        })
    }

    //
    // 
    processSearchKey(key: string): string {

        let queryKey = ''
        // const wordNum = 7
        // let words = tokenizer.extract(key, wordNum).map((v)=>{return v.word})
        // if(words.length < wordNum /2){
        // }
        key = key.replace(/[`:_.~!@#$%^&*() \+ =<>?"{}|, \/ ;' \\ [ \] ·~！@#￥%……&*（）—— \+ ={}|《》？：“”【】、；‘’，。、]/g,
            ' ');//标点符号仅作为分隔符

        let words = tokenizer.cutForSearch(key, false).filter((v) => {
            return !v.match(/^[a-zA-Z\s]$/)
        }).slice(0, 20)
        if (words.length < 15) {
            words.push(...tokenizer.cutHMM(key).filter((v) => {
                return !v.match(/^[a-zA-Z\s]$/) && !words.includes(v)
            }))
            words = words.slice(0, 20)
        }
        for (let i = 0; i < words.length; i++) {
            queryKey += words[i]
            if (i != words.length - 1) queryKey += '|'
        }
        return queryKey
        // return this.getPosts(paged,posts_per_page,null,subarea,null,null,queryKey,onlyTopic,'active_time','DESC')
        // console.log(words);
        // let searchStr = `(${queryKey})`
        // if(subarea){
        //     searchStr += `(@subarea:{${subarea}})`
        // }
        // if(onlyTopic){
        //     searchStr += `(@type:{1})`
        // }
        // let posts: Post[] = []
        // try {
        //     let res = await cache.ft.search(this.indexName, searchStr, {
        //         SORTBY: {BY: 'active_time' as any, DIRECTION: 'DESC'},
        //         LIMIT: {from: (paged - 1) * posts_per_page, size: posts_per_page},
        //         // VERBATIM:true
        //     })

        //     for (const v of res.documents) {
        //         // console.log(v);
        //         posts.push(v.value as any)
        //     }
        // } catch (e) {console.error(e)}
        // return posts
    }
    async getTopics(paged: number, posts_per_page: number, subarea?: number, type?: number,
        sortby: 'active_time' | 'create_time' | 'views' = 'active_time') {

        let key = `topics:${paged}:${posts_per_page}:${subarea}:${type}:${sortby}`
        let posts: {total: number, list: Post[]} = await cache.json.get(key) as any
        if (!posts) {
            posts = {total: 0, list: []}
            let searchStr = '(@type:{1})(-@topic:{null})'
            if (subarea) {
                let sa = await subareaDB.getSubareaById(subarea)

                if (!sa) return posts

                if (sa.need_moderate) {
                    // where.status = 1
                }
                searchStr += `(@subarea:{${subarea}})`
            }

            if (type) {
                searchStr += `(@topic_type:{${type}})`
            }
            searchStr += `(@deleted:{0})`
            
            let res = await cache.ft.search(this.indexName, searchStr, {
                SORTBY: {BY: sortby as any, DIRECTION: 'DESC'},
                LIMIT: {from: (paged - 1) * posts_per_page, size: posts_per_page},
            })
            posts = {total: res.total, list: []}

            for (const v of res.documents) {
                posts.list.push(v.value as any)
            }
        }
        cache.json.set(key, '$', posts as any)
        cache.expire(key, 5)

        return posts
    }
    // 以下三个函数用于统计帖子浏览量
    postViewsKey(pid: number) {
        return `postviews:${pid}`
    }
    async addPostViews(pid: number, label: string) {
        return cache.pfAdd(this.postViewsKey(pid), label)
    }
    async countPostViews(post: any) {
        post.views += await cache.pfCount(this.postViewsKey(post.pid))
        const now = Date.now()
        if ((now - post.lastCountViewsTime) > 300000) {// update every 5 minutes
            post.lastCountViewsTime = now
            await Promise.all([
                cache.del(this.postViewsKey(post.pid)),
                cache.json.set('post:' + post.pid, '$.lastCountViewsTime', post.lastCountViewsTime),
                cache.json.set('post:' + post.pid, '$.views', post.views),
                knex('posts').update({views: post.views}).where('pid', post.pid)
            ])
        }
    }

    async addOneSubpost(post: any) {
        return cache.json.numIncrBy('post:' + post.pid, '$.subpost_num', 1).then(() => {
            knex('post').where('pid', post.pid)
                .update({subPosts_num: post.subPosts + 1})
        })
    }

    async getSubPostsPidList(ppid: number, sortby = "active_time", direction = 'DESC') {
        let key = `subpostslist:${ppid}:${sortby}:${direction}`
        let posts: {total: number, list: any[]} = await cache.json.get(key) as any
        if (!posts) {
            let searchStr = ''
            if (ppid) {
                searchStr += `(@ppid:{${ppid}})`
            }
            searchStr += `(@deleted:{0})`
            // console.log(searchStr);
            const limit = 10000
            try {
                // 等有NOCONTENT就换成下面的
                // let res = await cache.ft.search(this.indexName, searchStr + "\" \"NOCONTENT", {
                //     NOCONTENT,
                //     SORTBY: {BY: sortby as any, DIRECTION: 'DESC'},
                //     LIMIT: {from: 0, size: limit},
                // })
                // for (const v of res.documents) {
                //     posts.list.push(v.value as any)
                // }
                let resList = await cache.sendCommand(["FT.SEARCH", this.indexName, searchStr, "NOCONTENT", "SORTBY", `${sortby}`, "DESC", "LIMIT", "0", `${limit}`]) as any[]
                posts = {total: resList.shift(), list: []}
                posts.list = posts.list.concat(resList)
                let total = posts.total + 1e-9
                for (let i = 1; i < total / limit; i++) {
                    // res = await cache.ft.search(this.indexName, searchStr, {
                    //     SORTBY: {BY: sortby as any, DIRECTION: 'DESC'},
                    //     LIMIT: {from: i * limit, size: limit},
                    // })
                    // for (const v of res.documents) {
                    //     posts.list.push(v.value as any)
                    // }
                    resList = await cache.sendCommand(["FT.SEARCH", this.indexName, searchStr, "NOCONTENT", "SORTBY", `${sortby}`, "DESC", "LIMIT", `${i * limit}`, `${limit}`])
                    resList.shift()
                    posts.list = posts.list.concat(resList)
                }
                posts.list = posts.list.map((v: String) => Number(v.slice(5)))
            } catch (e) {
                console.log("NONONO");
                console.log(e)
            }
            cache.json.set(key, '$', posts as any)
            cache.expire(key, 5)
            // console.log(posts.list.length);
        }

        return posts
    }
    // searchKey没经过校验，可能存在注入问题，请通过searchPosts来关键词搜索
    async getPosts(option: QueryPostOption) {
        let {paged, posts_per_page, topic, subarea, author, ppid, searchKey, onlyTopic, sortby, direction} = option

        onlyTopic ??= false
        sortby ??= 'active_time'
        direction ??= 'DESC'
        let key = `posts:${paged}:${posts_per_page}:${topic}:${subarea}:${author}:${ppid}:${searchKey}:${sortby}:${direction}`
        let posts: {total: number, list: Post[]} = await cache.json.get(key) as any
        if (!posts) {
            let searchStr = ''
            if (onlyTopic) {
                searchStr += `(@type:{1})`
            }
            if (topic) {
                let tp = await topicDB.getTopicById(topic)
                if (!tp) return null
                searchStr += `(@topic:${topic})`
            }
            if (subarea) {
                let sa = await subareaDB.getSubareaById(subarea)
                if (!sa) return null
                if (sa.need_moderate) {
                    // where.status = 1
                }
                searchStr += `(@subarea:{${subarea}})`
            }
            if (author) {
                searchStr += `(@author:{${author}})`
            }
            if (ppid) {
                searchStr += `(@ppid:{${ppid}})`
            }
            if (searchKey) {
                searchStr += `(${searchKey})`
            }

            searchStr += `(@deleted:{0})`
            // console.log(searchStr);

            let res = await cache.ft.search(this.indexName, searchStr, {
                SORTBY: {BY: sortby as any, DIRECTION: direction as any},
                LIMIT: {from: (paged - 1) * posts_per_page, size: posts_per_page},
            })
            posts = {total: res.total, list: []}

            for (const v of res.documents) {
                posts.list.push(v.value as any)
            }
            cache.json.set(key, '$', posts as any)
            cache.expire(key, 5)
            // console.log(posts.list.length);
        }

        return posts
    }

    async loadPostByid(pid: number) {
        await knex('posts').select().where('pid', pid).first().then(async p => {
            let post = p;
            this.processPostWhenLoading(post)
        })
    }

    async getModeratePosts(start_time: Date, end_time: Date, uid: number, subareas: number[], statuses: number[]) {
        let key = `mPosts:${start_time}:${end_time}:${uid}:${subareas}:${statuses}`
        let posts: Post[] = await cache.json.get(key) as any
        if (!posts) {
            // let postSelect = select.from('posts')
            let postSelect = knex('posts').select()
            if (start_time && end_time) {
                postSelect.whereBetween('create_time', [start_time, end_time])
            }
            if (uid) {
                postSelect.where('author', uid)
            }
            if (subareas) {
                postSelect.whereIn('subarea', subareas)
            }
            if (statuses) {
                postSelect.whereIn('status', statuses)
            }
            await postSelect.then(async ps => {
                posts = ps
            })
            cache.json.set(key, '$', posts as any)
            cache.expire(key, 5)
        }
        return posts
    }
}
export const postDB = new PostDB()
